
package com.hlkj.pay.service.merchant.impl;
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 */

import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.hlkj.framework.caches.service.RedisTemplateService;
import com.hlkj.framework.common.pojo.PageResult;
import com.hlkj.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.hlkj.framework.mybatis.core.query.QueryWrapperX;
import com.hlkj.framework.mybatis.core.utils.MyBatisUtils;
import com.hlkj.pay.common.constants.MerchantConstant;
import com.hlkj.pay.common.constants.RedisConstants;
import com.hlkj.pay.dto.merchant.MerchantInfoDto;
import com.hlkj.pay.dto.merchant.MerchantInfoQueryDto;
import com.hlkj.pay.dto.merchant.MerchantInfoRegisterDto;
import com.hlkj.pay.enums.CommonEnum;
import com.hlkj.pay.enums.MerchantEnum;
import com.hlkj.pay.infrastructure.mapper.finance.MerchantAmountMapper;
import com.hlkj.pay.infrastructure.mapper.merchant.MerchantConfigMapper;
import com.hlkj.pay.infrastructure.mapper.merchant.MerchantInfoMapper;
import com.hlkj.pay.infrastructure.model.finance.MerchantAmountDO;
import com.hlkj.pay.infrastructure.model.merchant.MerchantAppDO;
import com.hlkj.pay.infrastructure.model.merchant.MerchantInfoDO;
import com.hlkj.pay.service.CommonSnFilterService;
import com.hlkj.pay.service.merchant.IMerchantApplicationService;
import com.hlkj.pay.service.merchant.IMerchantService;
import com.hlkj.pay.util.MerchantUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author HlpayTeam
 * @date 2024/09/12 13:43
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class MerchantServiceImpl extends CommonSnFilterService implements IMerchantService {

    private final MerchantInfoMapper merchantInfoMapper;

    private final MerchantConfigMapper merchantConfigMapper;

    private final IMerchantApplicationService merchantApplicationService;

    private final MerchantAmountMapper merchantAmountMapper;

    private final RedisTemplateService redisTemplateService;

    @Override
    public String register(MerchantInfoRegisterDto merchantInfoRegisterDto) {
        MerchantEnum.MERCHANT_SN_TYPE from = MerchantEnum.MERCHANT_SN_TYPE.from(merchantInfoRegisterDto.getSnType());
        String sn = sn(from, 1);
        if (!StringUtils.hasText(sn)) {
            return "";
        }
        String appId = merchantApplicationService.generateAppId();
        if (!StringUtils.hasText(appId)) {
            return "";
        }
        merchantInfoRegisterDto.setSn(sn);
        MerchantInfoDO merchantInfoDO = merchantInfoRegisterDto;
        merchantInfoDO.setLegalPhone(merchantInfoRegisterDto.getPhone());
        // 默认开启
        merchantInfoDO.setStatus(CommonEnum.YES_NO_TYPE.YES.getCode());
        //默认为平台商户
        merchantInfoDO.setMerType(1);
        MerchantAppDO merchantAppDO = new MerchantAppDO();
        merchantAppDO.setSn(sn);
        merchantAppDO.setSysType(merchantInfoRegisterDto.getSysType().getCode());
        merchantAppDO.setAppId(appId);
        merchantAppDO.setMd5Key(DigestUtil.md5Hex(merchantInfoRegisterDto.getAccount() + merchantAppDO.getSn() + merchantAppDO.getAppId()));
        merchantAppDO.setDefaultAppFlag(CommonEnum.YES_NO_TYPE.YES.getCode());
        merchantAppDO.setAppName("默认应用");
        merchantAppDO.setStatus(CommonEnum.YES_NO_TYPE.YES.getCode());
        // String[] rsaKeys = SignUtils.generateRsaKey();
        // merchantAppDO.setPrivateKey(rsaKeys[0]);
        // merchantAppDO.setPublicKey(rsaKeys[1]);

        MerchantAmountDO merchantAmountDO = new MerchantAmountDO();
        merchantAmountDO.setSn(sn);
        merchantAmountDO.setMerchBalance(new BigDecimal(0));
        merchantAmountDO.setFrozenAmount(new BigDecimal(0));
        merchantAmountDO.setMarketAccount(new BigDecimal(0));
        merchantAmountDO.setFeeAmount(new BigDecimal(0));
        merchantAmountDO.setProfitAmount(new BigDecimal(0));
        merchantAmountDO.setTransitAmount(new BigDecimal(0));
        merchantAmountDO.setDepositAmount(new BigDecimal(0));
        merchantAmountDO.setSnType(merchantInfoRegisterDto.getSysType().getCode());

        merchantInfoMapper.insert(merchantInfoDO);
        merchantApplicationService.addApp(merchantAppDO);
        merchantAmountMapper.insert(merchantAmountDO);
        return sn;
    }

    @Override
    public MerchantInfoDO queryMerchantByAccount(String account) {
        return merchantInfoMapper.selectOne(MerchantInfoDO::getAccount, account);
    }

    @Override
    public List<MerchantInfoDO> queryList(MerchantInfoQueryDto merchantInfoQueryDto) {
        return merchantInfoMapper.selectList(buildWrapper(merchantInfoQueryDto));
    }

    @Override
    public List<MerchantInfoDO> queryList(List<String> snList) {
        MerchantInfoQueryDto merchantInfoQueryDto = new MerchantInfoQueryDto();
        merchantInfoQueryDto.setSnList(snList);
        return queryList(merchantInfoQueryDto);
    }

    @Override
    @Cacheable(value = RedisConstants.MERCHANT_INFO, key = "#sn", unless = "#result == null")
    public MerchantInfoDO queryMerchant(String sn) {
        MerchantInfoQueryDto merchantInfoQueryDto = new MerchantInfoQueryDto();
        merchantInfoQueryDto.setSn(sn);
        return queryOne(merchantInfoQueryDto);
    }

    @Override
    public MerchantInfoDO queryMerchantNoCache(String sn) {
        MerchantInfoQueryDto merchantInfoQueryDto = new MerchantInfoQueryDto();
        merchantInfoQueryDto.setSn(sn);
        return queryOne(merchantInfoQueryDto);
    }

    @Override
    @CacheEvict(value = RedisConstants.MERCHANT_INFO, key = "#merchantInfoDO.sn")
    public void updateMerchantBySn(MerchantInfoDO merchantInfoDO) {
        MerchantInfoQueryDto merchantInfoQueryDto = new MerchantInfoQueryDto();
        merchantInfoQueryDto.setSn(merchantInfoDO.getSn());
        merchantInfoMapper.update(merchantInfoDO, buildWrapper(merchantInfoQueryDto));
        // 更新配置逻辑
        if (merchantInfoDO.getSuperAgent() != null) {
            // 手动清理缓存
            delMerchantByAgentId(merchantInfoDO.getSuperAgent());
        }
    }

    @CacheEvict(value = RedisConstants.MERCHANT_AGENT, key = "#merchantInfoDO.sn")
    public void delMerchantByAgentId(String sn) {

    }

    @Override
    public PageResult<MerchantInfoDto> queryPage(MerchantInfoQueryDto merchantInfoQueryDto) {
        merchantInfoQueryDto.setSortingFields("a.id");
        IPage<MerchantInfoDto> mpPage = MyBatisUtils.buildPage(merchantInfoQueryDto);
        QueryWrapper<MerchantInfoDto> queryWrapper = buildMerchantWrapper(merchantInfoQueryDto);
        merchantInfoMapper.selectMerchantInfoPage(mpPage, queryWrapper);
        return new PageResult(mpPage.getRecords(), mpPage.getTotal(), mpPage.getCurrent(), mpPage.getSize(), mpPage.getPages());

    }

    @Override
    @CacheEvict(value = RedisConstants.MERCHANT_INFO, key = "#sn")
    public void delMerchant(String sn) {
        MerchantInfoQueryDto merchantInfoQueryDto = new MerchantInfoQueryDto();
        merchantInfoQueryDto.setSn(sn);
        merchantInfoMapper.delete(buildWrapper(merchantInfoQueryDto));
    }



    @Override
    @Cacheable(value = RedisConstants.MERCHANT_AGENT, key = "#sn", unless = "#result == null||#result.isEmpty()")
    public List<String> queryMerchantByAgentId(String sn) {
        MerchantInfoQueryDto merchantInfoQueryDto = new MerchantInfoQueryDto();
        merchantInfoQueryDto.setSuperAgent(sn);
        List<MerchantInfoDO> merchantInfoList = merchantInfoMapper.selectList(buildWrapper(merchantInfoQueryDto));
        if (CollectionUtils.isEmpty(merchantInfoList)) {
            return Collections.emptyList();
        }
        return merchantInfoList.stream().map(merchantInfoDO -> merchantInfoDO.getSn()).collect(Collectors.toList());
    }

    @Override
    public MerchantAmountDO queryMerchantAmount(String sn) {
        return merchantAmountMapper.selectOne(MerchantAmountDO::getSn, sn);
    }

    @Override
    public BigDecimal queryMerchantFeeAmount(String sn) {
        String amountKey = feeAmountKey(sn);
        if (redisTemplateService.hasKey(sn)) {
            Long amount = (Long) redisTemplateService.get(sn);
            return MerchantUtils.divide(new BigDecimal(amount), new BigDecimal(MerchantConstant.REDIS_SN_FEE_AMOUNT_ACCURACY));
        }
        MerchantAmountDO merchantAmountDO = queryMerchantAmount(sn);
        if (merchantAmountDO == null) {
            return null;
        }
        Long amount = merchantAmountDO.getFeeAmount().multiply(new BigDecimal(MerchantConstant.REDIS_SN_FEE_AMOUNT_ACCURACY)).longValue();
        redisTemplateService.set(amountKey, amount);
        return merchantAmountDO.getFeeAmount();
    }

    @Override
    public void updateMerchantFeeAmount(String sn, BigDecimal feeAmount, CommonEnum.YES_NO_TYPE changeType) {
        String amountKey = feeAmountKey(sn);
        if (!redisTemplateService.hasKey(amountKey)) {
            MerchantAmountDO merchantAmountDO = queryMerchantAmount(sn);
            BigDecimal multiply = null;
            switch (changeType) {
                case YES:
                    multiply = feeAmount.add(merchantAmountDO.getFeeAmount());
                    break;
                default:
                    multiply = feeAmount.subtract(merchantAmountDO.getFeeAmount());
            }
            Long amount = multiply.multiply(new BigDecimal(MerchantConstant.REDIS_SN_FEE_AMOUNT_ACCURACY)).longValue();
            redisTemplateService.set(amountKey, amount);
            return;
        }
        Long amount = feeAmount.multiply(new BigDecimal(MerchantConstant.REDIS_SN_FEE_AMOUNT_ACCURACY)).longValue();
        switch (changeType) {
            case YES:
                break;
            default:
                amount = amount * -1L;
        }
        redisTemplateService.incr(amountKey, amount);
    }

    @Override
    public void addMerchantAmount(MerchantAmountDO merchantAmountDO) {
        merchantAmountMapper.insert(merchantAmountDO);
    }

    @Override
    public void updateMerchantAmount(MerchantAmountDO merchantAmountDO) {
        merchantAmountMapper.updateById(merchantAmountDO);
    }

    @Override
    public void syncMerchantFeeAmount(String sn) {
        String amountKey = feeAmountKey(sn);
        if (!redisTemplateService.hasKey(amountKey)) {
            return;
        }
        Long amount = (Long) redisTemplateService.get(sn);
        if (amount == null) {
            amount = 0L;
        }
        BigDecimal bigDecimal = MerchantUtils.divide(new BigDecimal(amount), new BigDecimal(MerchantConstant.REDIS_SN_FEE_AMOUNT_ACCURACY));
        MerchantAmountDO merchantAmountDO = queryMerchantAmount(sn);
        
        MerchantAmountDO updateMerchantAmountDO = new MerchantAmountDO();
        updateMerchantAmountDO.setId(merchantAmountDO.getId());
        updateMerchantAmountDO.setFeeAmount(bigDecimal);
        merchantAmountMapper.updateById(updateMerchantAmountDO);
    }

    @Override
    public Long queryMerchantCount(MerchantInfoQueryDto merchantInfoQueryDto) {
        return merchantInfoMapper.selectCount(buildWrapper(merchantInfoQueryDto));
    }

    String sn(MerchantEnum.MERCHANT_SN_TYPE merchantSnType, int num) {
        if (num > 100) {
            return "";
        }
        String sn = MerchantUtils.sn(merchantSnType);
        MerchantInfoQueryDto merchantInfoQueryDto = new MerchantInfoQueryDto();
        merchantInfoQueryDto.setSn(sn);
        MerchantInfoDO merchantInfoDO = queryOne(merchantInfoQueryDto);
        if (merchantInfoDO != null) {
            return sn(merchantSnType, num + 1);
        }
        return sn;
    }

    MerchantInfoDO queryOne(MerchantInfoQueryDto merchantInfoQueryDto) {
        LambdaQueryWrapper<MerchantInfoDO> queryWrapper = buildWrapper(merchantInfoQueryDto);
        queryWrapper.last("limit 1");
        return merchantInfoMapper.selectOne(queryWrapper);
    }

    /**
     * user log flag cache key
     *
     * @param sn
     * @return
     */
    private String feeAmountKey(String sn) {
        StringBuilder sb = new StringBuilder(MerchantConstant.REDIS_SN_FEE_AMOUNT);
        // sb.append(tenantId);
        sb.append(StringPool.COLON);
        sb.append(sn);
        return sb.toString();
    }

    /**
     * 查询条件封装
     *
     * @param merchantInfoQueryDto
     * @return
     */
    LambdaQueryWrapper<MerchantInfoDO> buildWrapper(MerchantInfoQueryDto merchantInfoQueryDto) {
        filterSn(merchantInfoQueryDto);
        LambdaQueryWrapperX<MerchantInfoDO> queryWrapper = new LambdaQueryWrapperX<>();
        queryWrapper.eqIfPresent(MerchantInfoDO::getSn, merchantInfoQueryDto.getSn());
        queryWrapper.inIfPresent(MerchantInfoDO::getSn, merchantInfoQueryDto.getSnList());
        queryWrapper.eqIfPresent(MerchantInfoDO::getName, merchantInfoQueryDto.getName());
        queryWrapper.likeIfPresent(MerchantInfoDO::getName, merchantInfoQueryDto.getLikeName());
        queryWrapper.eqIfPresent(MerchantInfoDO::getShortName, merchantInfoQueryDto.getShortName());
        queryWrapper.likeIfPresent(MerchantInfoDO::getShortName, merchantInfoQueryDto.getLikeShortName());
        queryWrapper.eqIfPresent(MerchantInfoDO::getLegalName, merchantInfoQueryDto.getLegalName());
        queryWrapper.likeIfPresent(MerchantInfoDO::getLegalName, merchantInfoQueryDto.getLikeLegalName());
        queryWrapper.eqIfPresent(MerchantInfoDO::getLegalPhone, merchantInfoQueryDto.getLegalPhone());
        queryWrapper.likeIfPresent(MerchantInfoDO::getLegalPhone, merchantInfoQueryDto.getLikeLegalPhone());
        queryWrapper.eqIfPresent(MerchantInfoDO::getAccount, merchantInfoQueryDto.getAccount());
        queryWrapper.likeIfPresent(MerchantInfoDO::getAccount, merchantInfoQueryDto.getLikeAccount());
        queryWrapper.eqIfPresent(MerchantInfoDO::getSuperAgent, merchantInfoQueryDto.getSuperAgent());
        queryWrapper.likeIfPresent(MerchantInfoDO::getSuperAgent, merchantInfoQueryDto.getLikeSuperAgent());
        queryWrapper.eqIfPresent(MerchantInfoDO::getStatus, merchantInfoQueryDto.getStatus());
        queryWrapper.eqIfPresent(MerchantInfoDO::getSignStatus, merchantInfoQueryDto.getSignStatus());
        queryWrapper.eqIfPresent(MerchantInfoDO::getSnType, merchantInfoQueryDto.getSnType());
        queryWrapper.geIfPresent(MerchantInfoDO::getCreateTime, merchantInfoQueryDto.getStartQueryTime());
        queryWrapper.leIfPresent(MerchantInfoDO::getCreateTime, merchantInfoQueryDto.getEndQueryTime());
        return queryWrapper;
    }

    /**
     * 查询条件封装
     *
     * @param merchantInfoQueryDto
     * @return
     */
    QueryWrapper<MerchantInfoDto> buildMerchantWrapper(MerchantInfoQueryDto merchantInfoQueryDto) {
        filterSn(merchantInfoQueryDto);
        QueryWrapperX<MerchantInfoDto> queryWrapper = new QueryWrapperX<>();
        queryWrapper.eq("a.delete_flag", CommonEnum.YES_NO_TYPE.NO.getCode());
        queryWrapper.eqIfPresent( "a.sn", merchantInfoQueryDto.getSn());
        queryWrapper.inIfPresent( "a.sn", merchantInfoQueryDto.getSnList());
        queryWrapper.eqIfPresent( "a.name", merchantInfoQueryDto.getName());
        queryWrapper.likeIfPresent( "a.name", merchantInfoQueryDto.getLikeName());
        queryWrapper.eqIfPresent( "a.short_name", merchantInfoQueryDto.getShortName());
        queryWrapper.likeIfPresent( "a.short_name", merchantInfoQueryDto.getLikeShortName());
        queryWrapper.eqIfPresent( "a.legal_name", merchantInfoQueryDto.getLegalName());
        queryWrapper.likeIfPresent( "a.legal_name", merchantInfoQueryDto.getLikeLegalName());
        queryWrapper.eqIfPresent("a.legal_phone", merchantInfoQueryDto.getLegalPhone());
        queryWrapper.likeIfPresent( "a.legal_phone", merchantInfoQueryDto.getLikeLegalPhone());
        queryWrapper.eqIfPresent( "a.account", merchantInfoQueryDto.getAccount());
        queryWrapper.likeIfPresent("a.account", merchantInfoQueryDto.getLikeAccount());
        queryWrapper.eqIfPresent( "a.super_agent", merchantInfoQueryDto.getSuperAgent());
        queryWrapper.likeIfPresent( "a.super_agent", merchantInfoQueryDto.getLikeSuperAgent());
        queryWrapper.eqIfPresent( "a.status", merchantInfoQueryDto.getStatus());
        queryWrapper.eqIfPresent("a.sign_status", merchantInfoQueryDto.getSignStatus());
        queryWrapper.eqIfPresent( "a.sn_type", merchantInfoQueryDto.getSnType());
        queryWrapper.eqIfPresent( "a.type", merchantInfoQueryDto.getType());
        queryWrapper.eqIfPresent("a.mer_type", merchantInfoQueryDto.getMerType());
        queryWrapper.geIfPresent( "a.create_time", merchantInfoQueryDto.getStartQueryTime());
        queryWrapper.leIfPresent( "a.create_time", merchantInfoQueryDto.getEndQueryTime());
        return queryWrapper;
    }
}
